package com.ionehe.group.common.file.ex.service.impl;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.ionehe.group.common.file.FileServer;
import com.ionehe.group.common.file.ex.model.UploadDTO;
import com.ionehe.group.common.file.ex.repository.dataobject.UserFile;
import com.ionehe.group.common.file.ex.repository.dataobject.UserFolder;
import com.ionehe.group.common.file.ex.service.UserFileManageService;
import com.ionehe.group.common.file.ex.service.UserFileService;
import com.ionehe.group.common.file.ex.service.UserFolderService;
import com.ionehe.group.common.file.ex.utils.FileType;
import com.ionehe.group.common.file.ex.utils.FileUtil;
import com.ionehe.group.common.file.exception.FileException;
import com.ionehe.group.common.io.Response;
import com.ionehe.group.common.uaa.ex.enums.ErrorMessages;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.RequestParam;

import javax.annotation.Resource;
import javax.imageio.ImageIO;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static com.google.common.base.Preconditions.checkArgument;

/**
 * Copyright © 2020 杭州数美科技有限公司
 * Date: 2020/9/23
 * Time: 上午9:40
 *
 * @author 2020年 <a href="mailto:chenwd@shumei.ai">秀</a>
 * 【用户文件服务】
 */
@Service
@Slf4j
public class UserFiles implements UserFileManageService {
    private static final Set<String> ALLOWED_TYPES = ImmutableSet.of("jpeg", "jpg", "png", "gif", "bmp", "pdf");
    @Resource
    private UserFileService userFileService;
    @Resource
    private UserFolderService userFolderService;
    @Resource
    private FileServer fileServer;
    /**
     * 默认2M
     */
    @Value("${file.imageMaxSize:2097152}")
    private Long imageMaxSize;
    /**
     * 默认4M
     */
    @Value("${file.fileMaxSize:4194304}")
    private Long fileMaxSize;
    /**
     * 文件夹名称
     */
    @Value("${file.folder:-1}")
    private String fileFolder;

    /**
     * 上传文件
     */
    @Override
    public Response<List<UploadDTO>> upload(Long userId, Long folderId, List<File> files) {
        log.info("UserFiles[]upload[]start! userId:{}, userId:{}", userId, folderId);
        String group = "users";

        try {
            Response<List<UserFolder>> userFolderRes = userFolderService.findByCreatBy(userId);
            if (!userFolderRes.success() || Objects.isNull(userFolderRes.getData())) {
                return Response.no(ErrorMessages.ILLEGAL_PARAMS.toString());
            }

            List<UserFolder> usersFolder = userFolderRes.getData();

            if (CollectionUtils.isEmpty(usersFolder)) {
                // 创建文件夹
                UserFolder userFolder = new UserFolder();

                userFolder.setCreateBy(userId);
                userFolder.setCreatedAt(new Date());
                userFolder.setGroupName(group);
                userFolder.setPid(0L);
                userFolder.setFolder(userId.toString());
                userFolder.setHasChildren(Boolean.FALSE);

                Response<Long> folderRes = userFolderService.createFolder(userFolder);

                if (!folderRes.success() || Objects.isNull(folderRes.getData())) {
                    return Response.no(ErrorMessages.FOLDER_CREATE_FAIL.toString());
                }

                folderId = folderRes.getData();
            } else {
                // 一个用户只有一个文件夹
                folderId = usersFolder.get(0).getId();
            }

            List<UploadDTO> result = new ArrayList<>();

            for (File nextFile : files) {
                String fileRealName = nextFile.getName();

                if (Objects.equals(FileUtil.fileType(fileRealName), FileType.IMAGE)) {
                    //图片处理
                    result.add(upImage(userId, group, fileRealName, folderId, nextFile));
                } else {
                    //文件上传
                    result.add(upFile(userId, group, fileRealName, folderId, nextFile));
                }
            }

            return Response.yes(result);
        } catch (FileException fe) {
            log.error("UserFiles[]upload[]error! cause:{}", Throwables.getStackTraceAsString(fe));
            return Response.no(fe.getMessage());
        } catch (Exception e) {
            log.error("UserFiles[]upload[]error! cause:{}", Throwables.getStackTraceAsString(e));
            return Response.no(ErrorMessages.FILE_UPLOAD_FAIL.toString());
        }
    }

    /**
     * 文件修改(改名)
     *
     * @param userFile 文件
     * @return 返回文件的绝对路径
     */
    @Override
    public Response<Boolean> updateFile(Long userId, UserFile userFile) {
        try {
            checkArgument(Objects.nonNull(userFile.getId()), "file.id.null");
            checkAuthorize(userId, userFile.getId());

            Response<Boolean> result = userFileService.updateFile(userFile);

            if (!result.success()) {
                log.error("Update file failed, folder={}", userFile);
                throw new FileException(result.getMessage());
            }

            return Response.yes(result.getData());
        } catch (IllegalArgumentException e) {
            log.error("Check argument failed, userFile={}", userFile);
            throw new FileException(e.getMessage());
        }
    }

    /**
     * 文件移动
     *
     * @param id       文件编号
     * @param folderId 移动到的文件夹编号
     * @return Boolean
     * 返回文件移动结果
     */
    @Override
    public Response<Boolean> moveFile(Long userId, Long id, @RequestParam Long folderId) {
        checkAuthorize(userId, id);

        Response<UserFolder> folderRes = userFolderService.findById(folderId);

        if (!folderRes.success() || null == folderRes.getData()) {
            log.error("Don't exist folder with id={}, error code={}", folderId, folderRes.getMessage());
            throw new FileException(folderRes.getMessage());
        }

        if (Objects.equals(folderRes.getData().getCreateBy(), 1L)) {
            log.error("Can't delete folder={}, by userId={}", folderId, 1L);
            throw new FileException("authorize.no");
        }

        Response<Boolean> result = userFileService.moveFile(id, folderId);

        if (!result.success()) {
            log.error("Failed move file, fileId={}, folderId={}", id, folderId);
            throw new FileException(result.getMessage());
        }

        return Response.yes(result.getData());
    }

    /**
     * 删除文件
     *
     * @param id 文件id
     */
    @Override
    public Response<Boolean> delete(Long userId, Long id) {
        Response<UserFile> fileR = userFileService.deleteFile(id);

        if (!fileR.success()) {
            log.warn("failed to find userImage by imageId {} when delete", id);
            return Response.no(ErrorMessages.FILE_DELETE_FAIL.toString());
        }

        UserFile userFile = fileR.getData();

        // 身份校验
         checkAuthorize(userId, userFile.getCreateBy());

        try {
            fileServer.delete(userFile.getPath());
        } catch (Exception e) {
            log.error("Failed delete file:{}, error:{}", userFile, e);
            return Response.no(ErrorMessages.FILE_DELETE_FAIL.toString());
        }
        return Response.yes(Boolean.TRUE);
    }

    /**
     * 上传图片文件
     *
     * @param userId       用户编号
     * @param group        用户族
     * @param fileRealName 文件名称
     * @param folderId     文件夹编号
     * @param file         文件
     * @return UploadDto
     */
    private UploadDTO upImage(Long userId, String group, String fileRealName, Long folderId, File file) {
        String itemFolder = StrUtil.equals(fileFolder, "-1") ? "" : fileFolder + "/";

        UserFile image = new UserFile();

        image.setGroupName(group);
        image.setFileType(FileType.IMAGE.value());
        image.setCreateBy(userId);
        image.setName(fileRealName);
        image.setFolderId(folderId);

        String ext = cn.hutool.core.io.FileUtil.extName(fileRealName).toLowerCase();

        if (ALLOWED_TYPES.contains(ext)) {
            try {
                byte[] imageData = cn.hutool.core.io.FileUtil.readBytes(file);

                if (imageData.length > imageMaxSize) {
                    log.debug("image size {} ,maxsize {} ,the upload image is to large", imageData.length, imageMaxSize);
                    return new UploadDTO(image, "image.size.exceed" + imageMaxSize / (1024 * 1024) + "mb");
                }

                image.setSize((int) cn.hutool.core.io.FileUtil.size(file));
                image.setExtra(imageSize(imageData));

                // 上传文件
                String filePath = fileServer.write(itemFolder + FileUtil.newFileName(fileRealName), file);
                if (Strings.isNullOrEmpty(filePath)) {
                    log.error("write file(name={}) of user(id={}) to image server failed", fileRealName, userId);
                    throw new FileException(ErrorMessages.IMAGE_UPLOAD_FAIL.toString());
                }

                image.setPath(filePath);

                //若成功返回路径则代表上传成功
                boolean isSucceed = !Strings.isNullOrEmpty(filePath);
                if (!isSucceed) {
                    log.error("write file(name={}) of user(id={}) to image server failed", fileRealName, userId);
                    return new UploadDTO(image, "user.image.upload.no");
                }

                // 创建文件
                Response<UserFile> createRes = userFileService.createFile(image);

                if (!createRes.success()) {
                    log.error("Create image failed, upFile={}, error code={}", image, createRes.getMessage());
                    throw new FileException(createRes.getMessage());
                }

                UserFile userFile = createRes.getData();
                image.setId(userFile.getId());
                Files.delete(Paths.get(file.getPath()));
                return new UploadDTO(image);
            } catch (Exception e) {
                log.error("failed to process upload image {},cause:{}", fileRealName, Throwables.getStackTraceAsString(e));
                return new UploadDTO(image, "user.image.upload.no");
            }
        } else {
            return new UploadDTO(image, ErrorMessages.USER_IMAGES_ILLEGAL_EXT.toString());
        }
    }

    /**
     * 上传文件
     *
     * @param userId       用户编号
     * @param group        用户组
     * @param fileRealName 文件名称
     * @param folderId     文件夹编号
     * @param file         文件
     * @return UploadDto
     */
    private UploadDTO upFile(Long userId, String group, String fileRealName, Long folderId, File file) {
        String itemFolder = StrUtil.equals(fileFolder, "-1") ? "" : fileFolder + "/";

        UserFile upFile = new UserFile();

        upFile.setGroupName(group);
        upFile.setFileType(FileUtil.fileType(fileRealName).value());
        upFile.setCreateBy(userId);
        upFile.setName(fileRealName);
        upFile.setFolderId(folderId);

        try {
            long fileLength = file.length();
            if (fileLength > fileMaxSize) {
                log.warn("file size {} ,maxsize {} ,the upload file is to large", fileLength, fileMaxSize);
                return new UploadDTO(upFile, "file.size.exceed" + fileMaxSize / (1024 * 1024) + "mb");
            }

            upFile.setSize((int) cn.hutool.core.io.FileUtil.size(file));

            // 上传文件
            String filePath = fileServer.write(itemFolder + FileUtil.newFileName(fileRealName), file);

            if (Strings.isNullOrEmpty(filePath)) {
                log.error("write file(name={}) of user(id={}) to file server failed", fileRealName, userId);
            }

            upFile.setPath(filePath);

            //若成功返回路径则代表上传成功
            boolean isSucceed = !Strings.isNullOrEmpty(filePath);

            if (!isSucceed) {
                log.error("write file(name={}) of user(id={}) to file server failed", fileRealName, userId);
                throw new FileException(ErrorMessages.FILE_UPLOAD_FAIL.toString());
            }

            // 创建文件
            Response<UserFile> createRes = userFileService.createFile(upFile);

            if (!createRes.success()) {
                log.error("Create file failed, upFile={}, error code={}", upFile, createRes.getMessage());
                throw new FileException(createRes.getMessage());
            }

            UserFile userFile = createRes.getData();
            upFile.setId(userFile.getId());

            return new UploadDTO(upFile);
        } catch (Exception e) {
            log.error("failed to process upload file {},cause:{}", fileRealName, Throwables.getStackTraceAsString(e));
            return new UploadDTO(upFile, "user.file.upload.no");
        }
    }

    /**
     * 用户是否可更改文件
     *
     * @param userId   用户编号
     * @param folderId 文件编号
     */
    private void checkAuthorize(Long userId, Long folderId) {
        Response<UserFile> fileRes = userFileService.findById(folderId);

        if (!fileRes.success() || Objects.isNull(fileRes.getData())) {
            log.error("Don't exist folder with id={}, error code={}", folderId, fileRes.getMessage());
            throw new FileException(fileRes.getMessage());
        }

        if (!Objects.equals(fileRes.getData().getCreateBy(), userId)) {
            log.error("Can't delete folder={}, by userId={}", folderId, userId);
            throw new FileException("authorize.no");
        }
    }

    /**
     * 获取图片的尺寸
     *
     * @param imageData 图片数据
     * @return 返回尺寸
     */
    private String imageSize(byte[] imageData) {
        try {
            BufferedImage originalImage = ImageIO.read(new ByteArrayInputStream(imageData));

            Integer width = originalImage.getWidth();
            Integer height = originalImage.getHeight();

            return JSON.toJSONString(ImmutableMap.of("width", width, "height", height));
        } catch (IOException e) {
            log.error("Read image size failed, Error code={}", Throwables.getStackTraceAsString(e));
            return "";
        }
    }
}
